package org.thinginitself.mixpath.dom;

import org.jaxen.BaseXPath;
import org.jaxen.JaxenException;
import org.jaxen.Navigator;
import org.thinginitself.mixpath.MiXPathException;
import org.thinginitself.mixpath.MutableXPath;
import org.thinginitself.mixpath.Options;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;

/**
 * @author Andrew Harrison
 * @version 1.0.0 15/05/2011
 */
public class DomMutablePath extends BaseXPath implements MutableXPath {


    /**
     * Create a new <code>DOMXPath</code> from an XPath expression string.
     *
     * @param xpathExpr the XPath expression
     * @throws org.jaxen.JaxenException if there is a syntax error in the expression
     */
    public DomMutablePath(String xpathExpr) throws JaxenException {
        super(xpathExpr, DomMiXPathNavigator.getNavigator());
    }

    public DomMutablePath(String xpathExpr, Navigator navigator) throws JaxenException {
        super(xpathExpr, navigator);
    }


    @Override
    public boolean insertElement(Object node, String name, List selection) throws MiXPathException {
        Element el = createElement(node, name, null, null);
        return insertElement(el, selection);
    }

    @Override
    public boolean insertElement(Object node, String name, String namespace, List selection) throws MiXPathException {
        Element el = createElement(node, name, namespace, null);
        return insertElement(el, selection);
    }

    @Override
    public boolean insertElement(Object node, String name, String namespace, String text, List selection) throws MiXPathException {
        Element el = createElement(node, name, namespace, text);
        return insertElement(el, selection);
    }


    public boolean insertText(Object node, String text, List selection) throws MiXPathException {
        try {

            if (selection == null || selection.size() == 0) {
                return false;
            }
            for (Object o : selection) {
                if (o instanceof Element) {
                    Element e = (Element) o;
                    e.setTextContent(text);
                } else {
                    throw new MiXPathException("Result of select is not an Element:" + o.getClass().getName());
                }
            }
            return true;

        } catch (Exception e) {
            throw new MiXPathException(e);
        }
    }

    private Element createElement(Object node, String name, String namespace, String text) throws MiXPathException {
        Document owner = null;
        if (node instanceof Node) {
            owner = DomHelp.getOwnerDocument((Node) node);
        } else {
            throw new MiXPathException("unsupported context object");

        }
        Element ret = null;
        if (namespace == null) {
            ret = owner.createElement(name);
        } else {
            ret = owner.createElementNS(namespace, name);
            String pref = DomHelp.getPrefix((Node) node, namespace);
            if (pref != null) {
                ret.setPrefix(pref);
            }
        }
        if (text != null) {
            ret.setTextContent(text);
        }

        return ret;
    }


    private boolean insertElement(Element insert, List selection) throws MiXPathException {
        try {
            if (insert == null) {
                throw new MiXPathException("Cannot add content. Unsupported content type: " + insert.getClass().getName());
            }

            if (selection == null || selection.size() == 0) {
                return false;
            }
            Document ownerDoc = DomHelp.getOwnerDocument(insert);
            if (selection.size() == 1) {
                Object o = selection.get(0);
                if (o instanceof Element) {
                    Element e = (Element) o;
                    Document parent = e.getOwnerDocument();
                    if (parent != ownerDoc) {
                        e.appendChild(parent.importNode(insert, true));
                    } else {
                        e.appendChild(insert);
                    }
                } else if (o instanceof Document) {

                    Document parent = (Document) o;
                    if (parent.getDocumentElement() != null) {
                        parent.removeChild(parent.getDocumentElement());
                        System.out.println("DomMutablePath.insertElement PARENT ALREADY HAS A CHILD");
                    }
                    if (parent != ownerDoc) {
                        parent.appendChild(parent.importNode(insert, true));
                    } else {
                        parent.appendChild(insert);
                    }

                } else {
                    throw new MiXPathException("Result of select is not an Element:" + o.getClass().getName());
                }
            } else {
                for (Object o : selection) {
                    if (o instanceof Element) {
                        Element e = (Element) o;
                        Document parent = e.getOwnerDocument();
                        if (parent != ownerDoc) {
                            e.appendChild(parent.importNode(insert, true));
                        } else {
                            e.appendChild(insert.cloneNode(true));
                        }
                    } else {
                        throw new MiXPathException("Result of select is not an Element:" + o.getClass().getName());
                    }
                }
            }
            return true;
        } catch (Exception e) {
            throw new MiXPathException(e);
        }
    }


    public boolean insertAttribute(Object node, String name, String namespace, String value, List selection) throws MiXPathException {
        try {

            if (selection == null || selection.size() == 0) {
                return false;
            }
            for (Object o : selection) {
                if (o instanceof Element) {
                    Element e = (Element) o;
                    if (namespace == null) {
                        e.setAttribute(name, value);
                    } else {
                        e.setAttributeNS(namespace, name, value);
                    }
                } else {
                    throw new MiXPathException("Result of select is not an Element:" + o.getClass().getName());
                }
            }
            return true;

        } catch (Exception e) {
            throw new MiXPathException(e);
        }
    }

    public boolean insertAttribute(Object node, String name, String value, List selection) throws MiXPathException {
        return insertAttribute(node, name, null, value, selection);
    }

    public boolean removeAttribute(Object node, String name, List selection) throws MiXPathException {
        return removeAttribute(node, name, null, selection);
    }

    public boolean removeAttribute(Object node, String name, String namespace, List selection) throws MiXPathException {
        try {


            if (selection == null || selection.size() == 0) {
                return false;
            }
            for (Object o : selection) {
                if (o instanceof Element) {
                    Element e = (Element) o;
                    if (namespace == null) {
                        e.removeAttribute(name);
                    } else {
                        e.removeAttributeNS(namespace, name);
                    }
                } else {
                    throw new MiXPathException("Result of select is not an Element:" + o.getClass().getName());
                }
            }
            return true;
        } catch (Exception e) {
            throw new MiXPathException(e);
        }
    }

    public boolean removeElements(Object node, String name, List selection) throws MiXPathException {
        return removeElements(node, name, null, selection);
    }

    @Override
    public boolean removeElement(Object node) throws MiXPathException {
        if (node instanceof Node) {
            Node no = (Node) node;
            Node parent = no.getParentNode();
            if (parent != null) {
                parent.removeChild(no);
                return true;
            }
        } else {
            throw new MiXPathException("Result of select is not an Element:" + node.getClass().getName());
        }
        return false;
    }

    @Override
    public void writeTo(Object node, OutputStream stream, Options options) throws MiXPathException {
        if (node instanceof Node) {
            try {
                DomHelp.writeTo((Node) node, stream, options);
            } catch (IOException e) {
                throw new MiXPathException(e);
            }
        } else {
            throw new MiXPathException("unsupported context object: " + node.getClass());
        }
    }

    @Override
    public void writeTo(Object node, Writer stream, Options options) throws MiXPathException {
        if (node instanceof Node) {
            try {
                DomHelp.writeTo((Node) node, stream, new Options());
            } catch (IOException e) {
                throw new MiXPathException(e);
            }
        } else {
            throw new MiXPathException("unsupported context object: " + node.getClass());
        }
    }

    public boolean removeElements(Object node, String name, String namespace, List selection) throws MiXPathException {
        try {
            if (selection == null || selection.size() == 0) {
                return false;
            }
            for (Object o : selection) {
                if (o instanceof Node) {
                    Node no = (Node) o;
                    List<Element> toRemove = new ArrayList<Element>();
                    NodeList nl = no.getChildNodes();
                    for (int i = 0; i < nl.getLength(); i++) {
                        Node n = nl.item(i);
                        if (n instanceof Element) {
                            Element e = (Element) n;
                            if (namespace == null) {
                                if (e.getTagName().equals(name)) {
                                    toRemove.add(e);
                                }
                            } else {
                                if (e.getLocalName().equals(name) && e.getNamespaceURI().equals(namespace)) {
                                    toRemove.add(e);
                                }
                            }
                        }
                    }
                    for (Element e : toRemove) {
                        no.removeChild(e);
                    }
                } else {
                    throw new MiXPathException("Result of select is not an Element:" + o.getClass().getName());
                }
            }
            return true;
        } catch (Exception e) {
            throw new MiXPathException(e);
        }
    }

}
